<template>
    <!-- 虚拟列表容器，监听滚动事件 -->
    <div class="virtual-list" @scroll="handleScroll" ref="listRef">
        <!-- 内部容器，高度会根据内容动态计算 -->
        <div class="virtual-list__container" :style="containerStyle">
            <!-- 只渲染当前可见的项目 -->
            <div v-for="item in visibleItems" :key="item.id" class="virtual-list__item" :style="getItemStyle(item)"
                ref="itemsRef">
                <!-- 使用插槽让外部自定义项目渲染 -->
                <slot :item="item" />
            </div>
        </div>
    </div>
</template>

<script setup>
import { ref, computed, onMounted, watch } from 'vue'

// 定义组件属性
const props = defineProps({
    items: {  // 数据项数组
        type: Array,
        required: true
    },
    itemHeight: {  // 默认项目高度（固定高度模式使用）
        type: Number,
        default: 50
    },
    buffer: {  // 缓冲区大小，用于预渲染额外项目
        type: Number,
        default: 5
    },
    dynamic: {  // 是否使用动态高度模式
        type: Boolean,
        default: false
    }
})

// 引用DOM元素
const listRef = ref(null)  // 列表容器引用
const itemsRef = ref([])  // 项目元素引用数组
const scrollTop = ref(0)  // 当前滚动位置
const heightsCache = ref([])  // 动态高度模式下各项目的高度缓存
const observer = ref(null)  // ResizeObserver实例

// 初始化高度缓存
const initHeightsCache = () => {
    // 如果使用动态高度，初始时假设所有项目高度为默认高度
    heightsCache.value = Array(props.items.length).fill(props.itemHeight)
}

// 固定高度模式下计算可见项目
const getFixedVisibleItems = () => {
    // 计算起始索引（基于滚动位置和项目高度）
    const startIndex = Math.floor(scrollTop.value / props.itemHeight)
    // 计算结束索引（基于可视区域高度和缓冲区）
    const endIndex = Math.min(
        startIndex + Math.ceil(listHeight.value / props.itemHeight) + props.buffer,
        props.items.length - 1
    )
    // 返回可见项目（包括缓冲区）
    return props.items.slice(
        Math.max(0, startIndex - props.buffer),
        endIndex + 1
    )
}

// 动态高度模式下计算可见项目
const getDynamicVisibleItems = () => {
    let startIndex = 0  // 起始索引
    let endIndex = 0  // 结束索引
    let currentPosition = 0  // 当前累计高度

    // 找到第一个在可视区域内的项目
    while (
        startIndex < props.items.length - 1 &&
        currentPosition + heightsCache.value[startIndex] < scrollTop.value
    ) {
        currentPosition += heightsCache.value[startIndex]
        startIndex++
    }

    // 找到最后一个在可视区域内的项目
    endIndex = startIndex
    while (
        endIndex < props.items.length - 1 &&
        currentPosition < scrollTop.value + listHeight.value
    ) {
        currentPosition += heightsCache.value[endIndex]
        endIndex++
    }

    // 返回可见项目（包括缓冲区）
    return props.items.slice(
        Math.max(0, startIndex - props.buffer),
        Math.min(endIndex + props.buffer + 1, props.items.length)
    )
}

// 计算当前可见的项目
const visibleItems = computed(() => {
    if (!props.items.length) return []
    return props.dynamic ? getDynamicVisibleItems() : getFixedVisibleItems()
})

// 计算容器样式
const containerStyle = computed(() => ({
    height: props.dynamic
        // 动态高度模式下，容器高度为所有项目高度之和
        ? `${heightsCache.value.reduce((sum, h) => sum + h, 0)}px`
        // 固定高度模式下，容器高度为项目数量乘以项目高度
        : `${props.items.length * props.itemHeight}px`
}))

// 获取单个项目的样式
const getItemStyle = (item) => {
    const index = props.items.findIndex(i => i.id === item.id)
    if (index === -1) return {}

    if (props.dynamic) {
        // 动态高度模式下，计算项目的偏移位置
        const offset = heightsCache.value
            .slice(0, index)
            .reduce((sum, h) => sum + h, 0)
        return {
            position: 'absolute',
            top: `${offset}px`,
            width: '100%'
        }
    }
    // 固定高度模式下，直接计算位置
    return {
        height: `${props.itemHeight}px`,
        position: 'absolute',
        top: `${index * props.itemHeight}px`,
        width: '100%'
    }
}

// 处理滚动事件
const handleScroll = (e) => {
    scrollTop.value = e.target.scrollTop
}

// 计算列表可视区域高度
const listHeight = computed(() => listRef.value?.clientHeight || 0)

// 初始化ResizeObserver，用于监听项目高度变化
const initObserver = () => {
    if (!props.dynamic) return

    observer.value = new ResizeObserver(entries => {
        entries.forEach(entry => {
            const index = itemsRef.value.indexOf(entry.target)
            if (index !== -1) {
                const newHeight = entry.contentRect.height
                if (heightsCache.value[index] !== newHeight) {
                    // 更新高度缓存
                    heightsCache.value[index] = newHeight
                }
            }
        })
    })
}

// 开始观察项目高度变化
const observeItems = () => {
    if (!props.dynamic || !observer.value) return
    itemsRef.value.forEach(item => observer.value.observe(item))
}

// 组件挂载时初始化
onMounted(() => {
    initHeightsCache()
    initObserver()
    observeItems()
})

// 监听引用和属性变化
watch(itemsRef, observeItems)
watch(() => props.items, initHeightsCache)
</script>

<style scoped>
.virtual-list {
    height: 100%;
    overflow-y: auto;
    position: relative;
}

.virtual-list__container {
    position: relative;
}

.virtual-list__item {
    position: absolute;
    left: 0;
    right: 0;
}
</style>